package com.hzw.saas.common.util;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Date;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.hzw.saas.common.util.enums.StorageUnitEnum;
import com.hzw.saas.common.util.enums.TimeUnitEnum;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import org.apache.logging.log4j.util.Strings;
import lombok.AllArgsConstructor;
import lombok.Data;

/**
 * @author zzl
 * @since 04/12/2021
 */
public class UnitConvertUtils {

    private static final RoundingMode DEFAULT_ROUNDING_MODE = RoundingMode.HALF_UP;
    private static final String STORAGE_REGEX = "^(\\d+(\\.\\d+)?)([KMGTPE][B]?|[B])$";
    private static final String TIME_REGEX = "^(\\d+(\\.\\d+)?)([SMHDWmsY])$";

    private static final String ZERO_VALUE = "0";
    private static final String STORAGE_ZERO_VALUE = ZERO_VALUE + StorageUnitEnum.MEGABYTE.text();

    /**
     * 是否为存储值
     * 如 21K, 300M, 201.2G
     *
     * @param storage 存储值
     * @return true or false
     */
    public static boolean isStorage(String storage) {
        return matches(storage, STORAGE_REGEX);
    }


    /**
     * 获取存储值
     *
     * @param storage 存储值+存储单位
     * @return 存储值
     */
    public static String getStorageValue(String storage) {
        if (StrUtil.isBlank(storage))
            return storage;
        if (NumberUtil.isNumber(storage))
            return storage;
        Matcher matcher = assertMatcher(storage, STORAGE_REGEX);
        return matcher.group(1);
    }

    /**
     * 获取存储单位
     *
     * @param storage 存储值+存储单位
     * @return 存储单位
     */
    public static String getStorageUnit(String storage) {
        if (isBlankOrZero(storage))
            return StorageUnitEnum.MEGABYTE.text();
        Matcher matcher = assertMatcher(storage, STORAGE_REGEX);
        return matcher.group(matcher.groupCount());
    }

    /**
     * 存储值除法
     *
     * @param storage 存储值+存储单位
     * @param div     除数
     * @param scale   精确小数位数
     * @return 除法结果
     */
    public static String divStorage(String storage, Number div, int scale) {
        if (isBlankOrZero(storage))
            return ZERO_VALUE;
        ValueUnit valueUnit = getValueUnit(storage, STORAGE_REGEX);
        BigDecimal result = NumberUtil.div(new BigDecimal(valueUnit.getValue()), div);
        String resultValue = beautifyNumber(result, scale);
        return resultValue.concat(valueUnit.getUnit());
    }

    /**
     * 转换存储单位
     *
     * @param value   原数值
     * @param srcUnit 原单位
     * @param tarUnit 目标单位
     * @return 目标数值
     */
    public static BigDecimal convertStorage(String value, StorageUnitEnum srcUnit, StorageUnitEnum tarUnit) {
        if (isBlankOrZero(value))
            return BigDecimal.ZERO;
        BigDecimal scrByteValue = NumberUtil.mul(new BigDecimal(value), srcUnit.value());
        return NumberUtil.div(scrByteValue, tarUnit.value());
    }

    /**
     * 转换存储数值
     *
     * @param storage 存储值+存储单位，如1G，2K
     * @param tarUnit 目标单位
     * @return 目标数值
     */
    public static BigDecimal convertStorage(String storage, StorageUnitEnum tarUnit) {
        if (isBlankOrZero(storage))
            return BigDecimal.ZERO;
        ValueUnit valueUnit = getValueUnit(storage, STORAGE_REGEX);
        StorageUnitEnum unitEnum = StorageUnitEnum.assertInstance(valueUnit.getUnit());
        return convertStorage(valueUnit.getValue(), Objects.requireNonNull(unitEnum), Objects.requireNonNull(tarUnit));
    }


    /**
     * 比较存储大小
     * storage1 == storage2, return 0
     * storage1 > storage2, return 正数
     * storage1 < storage2, return 负数
     *
     * @param storage1 存储量1
     * @param storage2 存储量2
     * @return s1==s2 -> 0, s1>s2 -> positive, s1<s2 -> negative
     */
    public static int compareStorage(String storage1, String storage2) {
        BigDecimal b1 = convertStorage(storage1, StorageUnitEnum.BYTE);
        BigDecimal b2 = convertStorage(storage2, StorageUnitEnum.BYTE);
        return ObjectUtil.compare(b1, b2);
    }

    /**
     * 将存储大小转换为合适的单位显示
     * 小于等于0时，将返回0M
     *
     * @param byteSize  存储字节数
     * @param scaleSize 保留的小数位
     * @return 合适的value+unit
     */
    public static String suitableStorageText(Long byteSize, int scaleSize) {
        if (Objects.isNull(byteSize) || byteSize <= 0)
            return STORAGE_ZERO_VALUE;
        String byteSizeValue = String.valueOf(byteSize);
        if (byteSize < StorageUnitEnum.KILOBYTE.value())
            return byteSizeValue + StorageUnitEnum.BYTE.text();
        StorageUnitEnum srcUnit = StorageUnitEnum.BYTE;
        StorageUnitEnum tarUnit = null;
        if (byteSize < StorageUnitEnum.MEGABYTE.value()) {
            tarUnit = StorageUnitEnum.KILOBYTE;
        } else if (byteSize < StorageUnitEnum.GIGABYTE.value()) {
            tarUnit = StorageUnitEnum.MEGABYTE;
        } else if (byteSize < StorageUnitEnum.TERABYTE.value()) {
            tarUnit = StorageUnitEnum.GIGABYTE;
        } else if (byteSize < StorageUnitEnum.PETABYTE.value()) {
            tarUnit = StorageUnitEnum.TERABYTE;
        } else if (byteSize < StorageUnitEnum.EXABYTE.value()) {
            tarUnit = StorageUnitEnum.PETABYTE;
        } else {
            tarUnit = StorageUnitEnum.EXABYTE;
        }
        BigDecimal resultDecimal = convertStorage(byteSizeValue, srcUnit, tarUnit);
        String sizeValue = beautifyNumber(resultDecimal, scaleSize);
        return sizeValue + tarUnit.text();
    }

    /**
     * 是否为时间
     *
     * @param time value + unit
     * @return 是否为时间
     */
    public static boolean isDateTime(String time) {
        return matches(time, TIME_REGEX);
    }

    /**
     * 获取时间值
     *
     * @param time value + unit
     * @return 时间值
     */
    public static String getTimeValue(String time) {
        if (StrUtil.isBlank(time))
            return time;
        if (NumberUtil.isNumber(time))
            return time;
        Matcher matcher = assertMatcher(time, TIME_REGEX);
        return matcher.group(1);
    }

    /**
     * 获取时间单位
     *
     * @param time value + unit
     * @return 时间单位
     */
    public static String getTimeUnit(String time) {
        if (isBlankOrZero(time))
            return TimeUnitEnum.DAY.text();
        Matcher matcher = assertMatcher(time, TIME_REGEX);
        return matcher.group(matcher.groupCount());
    }

    /**
     * 转换时间单位数值
     * 年月季度的时间计算避免使用此方法
     *
     * @param value   时间值
     * @param srcUnit 原时间单位
     * @param tarUnit 目标时间单位
     * @return 目标时间值
     */
    public static BigDecimal convertDatetime(String value, TimeUnitEnum srcUnit, TimeUnitEnum tarUnit) {
        if (isBlankOrZero(value))
            return BigDecimal.ZERO;
        BigDecimal scrSecondsValue = NumberUtil.mul(new BigDecimal(value), srcUnit.value());
        return NumberUtil.div(scrSecondsValue, tarUnit.value());
    }

    /**
     * 转换时间单位数值
     * 年月季度的时间计算避免使用此方法
     *
     * @param time    原时间值+单位
     * @param tarUnit 目标时间单位
     * @return 目标时间值
     */
    public static BigDecimal convertDatetime(String time, TimeUnitEnum tarUnit) {
        if (isBlankOrZero(time))
            return BigDecimal.ZERO;
        ValueUnit valueUnit = getValueUnit(time, TIME_REGEX);
        TimeUnitEnum unitEnum = TimeUnitEnum.assertInstance(valueUnit.getUnit());
        return convertDatetime(valueUnit.getValue(), unitEnum, Objects.requireNonNull(tarUnit));
    }

    /**
     * 时间偏移
     *
     * @param start      开始时间
     * @param offsetTime 偏移量，含单位，只支持整数
     * @return 偏移时间
     */
    public static Date offsetDatetime(Date start, String offsetTime) {
        if (isBlankOrZero(offsetTime))
            return start;
        ValueUnit valueUnit = getValueUnit(offsetTime, TIME_REGEX);
        BigDecimal valueBigDecimal = new BigDecimal(valueUnit.getValue());
        int value = valueBigDecimal.intValue();
        return offsetDatetime(start, value, valueUnit.getUnit());
    }

    /**
     * 时间偏移
     *
     * @param start 开始时间
     * @param value 偏移值
     * @param unit  偏移单位
     * @return 偏移时间
     */
    public static Date offsetDatetime(Date start, Integer value, String unit) {
        if (Objects.isNull(value))
            return start;
        TimeUnitEnum unitEnum = TimeUnitEnum.assertInstance(unit);
        switch (unitEnum) {
            case SECOND:
                return DateUtil.offsetSecond(start, value);
            case MINUTE:
                return DateUtil.offsetMinute(start, value);
            case HOUR:
                return DateUtil.offsetHour(start, value);
            case DAY:
                return DateUtil.offsetDay(start, value);
            case WEEK:
                return DateUtil.offsetWeek(start, value);
            case MONTH:
                return DateUtil.offsetMonth(start, value);
            case SEASON:
                return DateUtil.offsetMonth(start, value * 3);
            case YEAR:
                return DateUtil.offsetMonth(start, value * 12);
        }
        return start;
    }

    /**
     * 比较时间单位大小
     * time1 == time2, return 0
     * time1 > time2, return 正数
     * time1 < time2, return 负数
     *
     * @param time1 t1
     * @param time2 t2
     * @return t1==t2 -> 0, t1>t2 -> positive, t1<t2 -> negative
     */
    public static int compareDatetime(String time1, String time2) {
        BigDecimal b1 = convertDatetime(time1, TimeUnitEnum.SECOND);
        BigDecimal b2 = convertDatetime(time2, TimeUnitEnum.SECOND);
        return ObjectUtil.compare(b1, b2);
    }

    /**
     * 用合适的数值显示bigDecimal
     *
     * @param value value
     * @return 合适的数值字符
     */
    public static String beautifyNumber(BigDecimal value, int scaleSize) {
        if (value == null)
            return Strings.EMPTY;
        value = value.setScale(scaleSize, DEFAULT_ROUNDING_MODE);
        if (new BigDecimal(value.intValue()).compareTo(value) == 0)
            return String.valueOf(value.intValue());
        return String.valueOf(value.doubleValue());
    }


    // ================================================ private method ================================================ //

    private static boolean isBlankOrZero(String size) {
        return StrUtil.isBlank(size) || size.trim().equals("0");
    }

    private static boolean matches(String str, String regEx) {
        try {
            Pattern pattern = Pattern.compile(regEx, Pattern.CASE_INSENSITIVE);
            Matcher matcher = pattern.matcher(str.trim());
            return matcher.matches();
        } catch (Exception e) {
            return false;
        }
    }

    private static void assertMatches(String str, String regEx) {
        boolean matches = matches(str, regEx);
        Assert.isTrue(matches, "string is in wrong format: ".concat(str));
    }

    private static void assertNotMatches(String str, String regEx) {
        boolean matches = matches(str, regEx);
        Assert.isFalse(matches, "string cannot be the format: ".concat(str));
    }

    private static Matcher getMatcher(String str, String regEx) {
        Pattern pattern = Pattern.compile(regEx, Pattern.CASE_INSENSITIVE);
        return pattern.matcher(str.trim());

    }

    private static Matcher assertMatcher(String str, String regEx) {
        Matcher matcher = getMatcher(str, regEx);
        Assert.isTrue(matcher.matches(), "string is in wrong format：".concat(str));
        return matcher;
    }

    private static ValueUnit getValueUnit(String str, String regEx) {
        Matcher matcher = assertMatcher(str, regEx);
        String valueStr = matcher.group(1);
        String unitStr = matcher.group(matcher.groupCount());
        if (unitStr.length() > 1)
            unitStr = unitStr.substring(0, 1);
        return new ValueUnit(valueStr, unitStr);
    }

    @Data
    @AllArgsConstructor
    private static class ValueUnit {
        private String value;
        private String unit;
    }

}
